////////////////////////////////////////////////////////////////////////////
//	D2DGraphicsWrapper.h & D2DGraphicsWrapper.cpp
//		Class that serves as a wrapper to all Direct2D interfaces and the
//		Windows Multimedia Library. It contains functions to load and draw
//		bitmaps and some D2D primitives.
//
//	Author:					Daniel Lima - April 2012
/////////////////////////////////////////////////////////////////////////////

#include "D2DGraphicsWrapper.h"

D2DGraphicsWrapper* D2DGraphicsWrapper::sm_pInstance = nullptr;

// Constructor - Initialize all variables to default values
D2DGraphicsWrapper::D2DGraphicsWrapper(void)
{
	m_pD2DFactory	= nullptr;
	m_pRenderTarget = nullptr;
	m_pBrush		= nullptr;
	m_matBox		= D2D1::IdentityMatrix();

	// DirectWrite Interfaces:
	m_pDWFactory	= nullptr;
	m_pTextFormat	= nullptr;
}

// Used to access the singleton's instance
D2DGraphicsWrapper* D2DGraphicsWrapper::GetInstance(void)
{
	if(!sm_pInstance)
		sm_pInstance = new D2DGraphicsWrapper;

	return sm_pInstance;
}

// Used to delete the singleton's instance
void D2DGraphicsWrapper::DeleteInstance(void)
{
	if(sm_pInstance)
	{
		delete sm_pInstance;
		sm_pInstance = nullptr;
	}
}

// Used to initialize all graphic interfaces
HRESULT D2DGraphicsWrapper::Initialize(HWND hWnd)
{
	// Initialize the COM Library.
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

	// Initialize the Direct2D Factory.
	HRESULT hResult;
	hResult = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
	if (FAILED(hResult))
	{
		MessageBox(HWND_DESKTOP, _T("ERROR: Failed to Create the Direct2D Factory."),
			_T("Direct2D Error"), MB_OK | MB_ICONERROR);
		return hResult;
	}

	// Get the dimensions of the client.
	RECT rClient;
	GetClientRect(hWnd, &rClient);

	// Initialize a Direct2D Size Structure.
	D2D1_SIZE_U size = D2D1::SizeU(rClient.right, rClient.bottom);

	// Create the Direct2D Render Target.
	hResult = m_pD2DFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
		D2D1::HwndRenderTargetProperties(hWnd, size), &m_pRenderTarget);
	if (FAILED(hResult))
	{
		MessageBox(HWND_DESKTOP, _T("ERROR: Failed to Create the Direct2D Render Target."),
			_T("Direct2D Error"), MB_OK | MB_ICONERROR);
		return hResult;
	}
	m_pRenderTarget->SetDpi(96.0f, 96.0f);

	// Create the Direct2D Solid Color Brush.
	hResult = m_pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(0x0), &m_pBrush);
	if (FAILED(hResult))
	{
		MessageBox(HWND_DESKTOP, _T("ERROR: Failed to Create the Direct2D Solid Color Brush."),
			_T("Direct2D Error"), MB_OK | MB_ICONERROR);
		return hResult;
	}

	// Initialize the DirectWrite Factory.
	hResult = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
			(IUnknown**)&m_pDWFactory);
	if (FAILED(hResult))
	{
		MessageBox(HWND_DESKTOP, _T("ERROR: Failed to Create the DirectWrite Factory."),
			_T("DirectWrite Error"), MB_OK | MB_ICONERROR);
		return hResult;
	}

	// Create the DirectWrite Text Format.
	hResult = m_pDWFactory->CreateTextFormat(_T("Veranda"), NULL,
		DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
		24, _T(""), &m_pTextFormat);
	if (FAILED(hResult))
	{
		MessageBox(HWND_DESKTOP, _T("ERROR: Failed to Create the DirectWrite Text Format."),
			_T("DirectWrite Error"), MB_OK | MB_ICONERROR);
		return hResult;
	}
	m_pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);

    // Create WIC factory.
    hResult = CoCreateInstance(
        CLSID_WICImagingFactory,
        NULL,
        CLSCTX_INPROC_SERVER,
        IID_PPV_ARGS(&m_pWICFactory)
        );

	return S_OK;
}

// Creates a Direct2D bitmap from the specified file name.
ID2D1Bitmap* D2DGraphicsWrapper::LoadBitmap(PCWSTR wszFilename,
		UINT uiDestWidth, UINT uiDestHeight)
{
	IWICBitmapDecoder*		pDecoder = NULL;
    IWICBitmapFrameDecode*	pSource = NULL;
    IWICStream*				pStream = NULL;
    IWICFormatConverter*	pConverter = NULL;
    IWICBitmapScaler*		pScaler = NULL;
	ID2D1Bitmap*			pBitmap = nullptr;

    HRESULT hResult = m_pWICFactory->CreateDecoderFromFilename(
        wszFilename, NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &pDecoder);
        
    if (SUCCEEDED(hResult))
    {
        // Create the initial frame.
        hResult = pDecoder->GetFrame(0, &pSource);
    }
    if (SUCCEEDED(hResult))
    {

        // Convert the image format to 32bppPBGRA
        // (DXGI_FORMAT_B8G8R8A8_UNORM + D2D1_ALPHA_MODE_PREMULTIPLIED).
        hResult = m_pWICFactory->CreateFormatConverter(&pConverter);

    }
 
 
    if (SUCCEEDED(hResult))
    {
        // If a new width or height was specified, create an
        // IWICBitmapScaler and use it to resize the image.
        if (uiDestWidth != 0 || uiDestHeight != 0)
        {
            UINT originalWidth, originalHeight;
            hResult = pSource->GetSize(&originalWidth, &originalHeight);
            if (SUCCEEDED(hResult))
            {
                if (uiDestWidth == 0)
                {
                    FLOAT scalar = static_cast<FLOAT>(uiDestHeight) / static_cast<FLOAT>(originalHeight);
                    uiDestWidth = static_cast<UINT>(scalar * static_cast<FLOAT>(originalWidth));
                }
                else if (uiDestHeight == 0)
                {
                    FLOAT scalar = static_cast<FLOAT>(uiDestWidth) / static_cast<FLOAT>(originalWidth);
                    uiDestHeight = static_cast<UINT>(scalar * static_cast<FLOAT>(originalHeight));
                }

                hResult = m_pWICFactory->CreateBitmapScaler(&pScaler);
                if (SUCCEEDED(hResult))
                {
                    hResult = pScaler->Initialize(
                            pSource,
                            uiDestWidth,
                            uiDestHeight,
                            WICBitmapInterpolationModeCubic
                            );
                }
                if (SUCCEEDED(hResult))
                {
                    hResult = pConverter->Initialize(
                        pScaler,
                        GUID_WICPixelFormat32bppPBGRA,
                        WICBitmapDitherTypeNone,
                        NULL,
                        0.f,
                        WICBitmapPaletteTypeMedianCut
                        );
                }
            }
        }
        else // Don't scale the image.
        {
            hResult = pConverter->Initialize(
                pSource,
                GUID_WICPixelFormat32bppPBGRA,
                WICBitmapDitherTypeNone,
                NULL,
                0.f,
                WICBitmapPaletteTypeMedianCut
                );
        }
    }
    if (SUCCEEDED(hResult))
    {
    
        // Create a Direct2D bitmap from the WIC bitmap.
        hResult = m_pRenderTarget->CreateBitmapFromWicBitmap(
            pConverter,
            NULL,
            &pBitmap
            );
    }

    SafeRelease(&pDecoder);
    SafeRelease(&pSource);
    SafeRelease(&pStream);
    SafeRelease(&pConverter);
    SafeRelease(&pScaler);

    return pBitmap;
}

// Draw a bitmap
void D2DGraphicsWrapper::DrawBitmap(ID2D1Bitmap* pBitmap, const D2D1_RECT_F& DestRect, float fOpacity,
	D2D1_BITMAP_INTERPOLATION_MODE interpolationMode, const D2D1_RECT_F* pSourceRect)
{
	m_pRenderTarget->DrawBitmap(pBitmap, DestRect, fOpacity, interpolationMode, pSourceRect);
}


// Load bitmap in the bitmap array
int D2DGraphicsWrapper::Load(PCWSTR wszFilename, UINT uiDestWidth, UINT uiDestHeight)
{
	ID2D1Bitmap* pImg = LoadBitmap(wszFilename, uiDestWidth, uiDestHeight);

	if(!pImg) return -1;

	m_vpBitmapManager.push_back(pImg);
	return m_vpBitmapManager.size() - 1;
}

// Render bitmap from the bitmap array
void D2DGraphicsWrapper::Draw(int nBitmapID, const RECT& rectDestination, float fOpacity, const RECT* rectSource)
{
	if(nBitmapID == -1 || nBitmapID >= m_vpBitmapManager.size())
		return;

	D2D1_RECT_F d2d_rectD;
	d2d_rectD.top = rectDestination.top;
	d2d_rectD.left = rectDestination.left;
	d2d_rectD.bottom = rectDestination.bottom;
	d2d_rectD.right = rectDestination.right;

	D2D1_RECT_F* d2d_rectS = new D2D1_RECT_F;

	if(rectSource)
	{
		d2d_rectS->top = rectSource->top;
		d2d_rectS->left = rectSource->left;
		d2d_rectS->bottom = rectSource->bottom;
		d2d_rectS->right = rectSource->right;
	}
	else
		d2d_rectS = nullptr;


	DrawBitmap(m_vpBitmapManager[nBitmapID], d2d_rectD, fOpacity,
		D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, d2d_rectS);
}

void D2DGraphicsWrapper::Draw(int nBitmapID, float fPosX, float fPosY, float fOpacity, const RECT* rectSource)
{
	if(nBitmapID == -1 || nBitmapID >= m_vpBitmapManager.size())
		return;

	D2D1_RECT_F d2d_rectD;
	d2d_rectD.top = fPosY;
	d2d_rectD.left = fPosX;

	if(!rectSource)
	{
		d2d_rectD.bottom = fPosY + m_vpBitmapManager[nBitmapID]->GetSize().height;
		d2d_rectD.right = fPosX + m_vpBitmapManager[nBitmapID]->GetSize().width;
	}
	else
	{
		d2d_rectD.bottom = fPosY + (rectSource->bottom - rectSource->top);
		d2d_rectD.right = fPosX + (rectSource->right - rectSource->left);
	}

	D2D1_RECT_F* d2d_rectS = new D2D1_RECT_F;
	if(rectSource)
	{
		d2d_rectS->top = rectSource->top;
		d2d_rectS->left = rectSource->left;
		d2d_rectS->bottom = rectSource->bottom;
		d2d_rectS->right = rectSource->right;
	}
	else
		d2d_rectS = nullptr;

	DrawBitmap(m_vpBitmapManager[nBitmapID], d2d_rectD, fOpacity,
		D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, d2d_rectS);
}

// Used to uninitialize all graphic interfaces
void D2DGraphicsWrapper::Shutdown()
{
	SafeRelease(&m_pWICFactory);
	SafeRelease(&m_pTextFormat);
	SafeRelease(&m_pDWFactory);
	SafeRelease(&m_pBrush);
	SafeRelease(&m_pRenderTarget);
	SafeRelease(&m_pD2DFactory);

	// Uninitialize the COM Library.
	CoUninitialize();

	DeleteInstance();
}

void D2DGraphicsWrapper::StartDrawing(void)
{
	m_pRenderTarget->BeginDraw();

	m_pRenderTarget->Clear();
}

void D2DGraphicsWrapper::StopDrawing(HWND hWnd)
{
	HRESULT hResult = m_pRenderTarget->EndDraw();

	/*if(hResult == D2DERR_RECREATE_TARGET)
	{
		Shutdown();
		Initialize(hWnd);
	}*/
}